#include "GLFWApp.h"
#include "shapes3d.h"
#include "OpenGLHelpers.h"
#include "CollidableCamera.h"

#include "sphere.h"

#include <vec3f.hpp>
#include <vec4f.hpp>
using siut::simd::standard::Vec4f;
#include <mat4f.hpp>
using siut::simd::standard::Mat4f;

#include <fstream>
#include "EATLevel.h"
#include "BitmapFont.h"

class GameManager : public GLFWApp
{
public:
	GameManager() : GLFWApp()
	{
		InitCharset();

		glClear(GL_COLOR_BUFFER_BIT);
		setOrthoProjection();
		
		Print("Reticulating splines...", xres/2-strlen("Reticulating splines...")*6*0.5f, yres/2, 0xff0000);
		glfwSwapBuffers();
		if( !level1.load() )
		{
			exit(1);
		}

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glusPerspective(60, xres/double(yres), .05, 100);
		glMatrixMode(GL_MODELVIEW);

		glEnable(GL_DEPTH_TEST);
		glDepthFunc( GL_LEQUAL );  // GL_LESS is default, using LEQUAL do draw triangles at same coords, but paint over. Used in triangle hit debug

		glEnable(GL_COLOR_MATERIAL);

		float fogColor[4] = {0.0, 0.0, 0.0, 1.0f};			// Let's make the Fog Color black too

		glFogi(GL_FOG_MODE, GL_EXP2);						
		glFogfv(GL_FOG_COLOR, fogColor);
		glFogf(GL_FOG_DENSITY, 0.095f);	
		glHint(GL_FOG_HINT, GL_DONT_CARE);
		glFogf(GL_FOG_START, 0);		// Set The Fog's Start Depth
		glFogf(GL_FOG_END, 25.0f);		// Set The Fog's End Depth
		glEnable(GL_FOG);

		myCamera.posx = 0;
		myCamera.posy = 2;
		myCamera.posz = 0;
		myCamera.hang = 0;
		myCamera.vang = 0;

		glfwSwapInterval(1); // Vsync?
		immidiateMode = false;

		mouseXSpeed = mouseYSpeed = 0;
		oldMousx = Input::mouseX();
		oldMousy = Input::mouseY();

		mainloop();
	}
protected:
	virtual void render()
	{
		glClearColor( .2,.2,.7,1);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glColor3f(1,0,0);
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);

		//float angle = float(Mouse.Mx)/xres * 6.28;
		float angle = 0;
		float position[] = { sin(angle)*15, cos(angle)*15, 0, 1.0f }; // 1 pos, 0 vec
		

		glPushMatrix();
		glDisable(GL_LIGHTING);
		glColor3f(1,1,1);
		glTranslatef(position[0], position[1], position[2]);
		Shapes3D::DrawSphere(.5,8,8);
		glEnable(GL_LIGHTING);
		glPopMatrix();


		GLfloat spc[] = {1.0, 1.0, 1.0, 1.0};
		glLightfv(GL_LIGHT0, GL_SPECULAR, spc);

		GLfloat amb[] = {.6f, .4f, .4f, 1.0};
		glLightfv(GL_LIGHT0, GL_AMBIENT, amb);

		GLfloat col[] = {.88f, .88f, .88f, 1.0};
		glLightfv(GL_LIGHT0, GL_DIFFUSE, col);

		glLightfv(GL_LIGHT0, GL_POSITION, position);


		updateCameraControls();


		double collisionTimeSpent = glfwGetTime();

		float sphRadi = 0.01;
		Sphere mySph( Vec3f( myCamera.posx+myCamera.dirx , myCamera.posy + myCamera.diry, myCamera.posz+myCamera.dirz  ), sphRadi );
		Vec3f sphVel( myCamera.dirx , myCamera.diry, myCamera.dirz );
		sphVel *= 0.001;
		
		hitTriangleIdx.clear();
		// Uses Igors code, intr_spheretri.h
		//if( false )
		//{
		//	bool collide = false;
		//	
		//	const Vec3f *triangle[3];
		//	float minDist = 1e32;
		//	for(size_t i=0; i<verts.size(); i+=3)
		//	{
		//		triangle[0] = &verts[i+0];
		//		triangle[1] = &verts[i+1];
		//		triangle[2] = &verts[i+2];

		//		Vec3f triNormal( normals[i][0], normals[i][1], normals[i][2] );

		//		float distTravel;
		//		Vec3f reaction;
		//		collide = testIntersectionTriSphere( triangle, triNormal, mySph, sphVel, distTravel, &reaction );
		//		if( distTravel < minDist) minDist = distTravel;


		//		if( collide ){
		//			hitTriangleIdx.push_back(i);
		//		}
		//	}

		//	myCamera.checkCollision( &verts[0], &aggroNormals[0], verts.size() );
		//}

		collisionTimeSpent = glfwGetTime() - collisionTimeSpent;

		glLoadMatrixf( myCamera.getMatrix() );
		


		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		static bool wireframe = false;
		if( Input::keyHit('1') ) wireframe = !wireframe;
		if( wireframe )
		{
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		}else{
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		}
		level1.render();

		char stringFoo[200];
		sprintf_s( stringFoo, 200, "msec spent in collision: %.3f, | fps = %.2f", 1000.0f * collisionTimeSpent, fps  );
		glfwSetWindowTitle( stringFoo );

		drawCollisionDebug();

		// Draw 2D
		{ 
		// Get the matrices and viewport
		Mat4f modelView;
		Mat4f projection;
		float depthRange[2];

		glGetFloatv(GL_MODELVIEW_MATRIX, modelView.v_ );
		glGetFloatv(GL_PROJECTION_MATRIX, projection.v_ );
		glGetFloatv(GL_DEPTH_RANGE, depthRange );
		
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();

		glLoadIdentity();
		glOrtho(0,xres,yres,0,-1,1);
		glMatrixMode(GL_MODELVIEW);

		glDisable(GL_POINT_SMOOTH);
		glPointSize(1.0f);

		glPushMatrix();
		glLoadIdentity();
		for( size_t i=0; i<level1.displayObjects.size(); i++)
		{
			DisplayObject &dispObj = level1.displayObjects[i];
			Vec3f dispObjPos = dispObj.getPos();
			Vec4f pos( dispObjPos.x(), dispObjPos.y(), dispObjPos.z(), 1.0f);
			Vec4f mvp = projection * (modelView*pos);
			Vec4f ndc = mvp / mvp.w();

			Vec4f newPos = mvp * pos;
			newPos.x() *= xres;
			newPos.y() *= yres;

			float zWindow = (depthRange[1] - depthRange[0])/2.0f * ndc.z() + (depthRange[1] + depthRange[0])/2.0f;

			if( zWindow > 1.0f ) continue;

			Print(level1.displayObjects[i].getName(), xres/2.0f + ndc.x() * xres/2.0f , yres/2.0f - ndc.y() * yres/2.0f, 0x00ff00);
		}
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		}

		glMatrixMode(GL_MODELVIEW);

		if( Input::keyHit(GLFW_KEY_SPACE) )
		{
			immidiateMode = !immidiateMode;

			if( immidiateMode )
				printf("slow\n");
			else
				printf("fast\n");

		}


		mouseXSpeed = Input::mouseX()- oldMousx;
		mouseYSpeed = Input::mouseY() - oldMousy;

		oldMousx = Input::mouseX();
		oldMousy = Input::mouseY();

		glfwSwapBuffers();
	}

	void updateCameraControls() 
	{
		int vx = glfwGetKey( 'D' )
			- glfwGetKey( 'A' );

		int vz = glfwGetKey( 'S' )
			- glfwGetKey( 'W' );

		int vy = glfwGetKey( 'Q' )
			- glfwGetKey( 'E' );

		float spd = 5;
		if( glfwGetKey(GLFW_KEY_LSHIFT))
		{
			vx *= spd;
			vy *= spd;
			vz *= spd;
		}

		float hang = 0;
		float vang = 0;
		if( Input::MouseDown(GLFW_MOUSE_BUTTON_LEFT) )
		{
			hang = -mouseXSpeed * dt * 0.5;
			vang = mouseYSpeed * dt * 0.5;
		}

		myCamera.update(vx * dt ,vy * dt ,vz * dt , hang, vang);
	}

	void drawCollisionDebug() 
	{
		if( hitTriangleIdx.size() > 0 )
		{
			// TODO, get .EAT mesh and display hit tris
			glDisable(GL_LIGHTING);
			throw("not implemented! Test against blender world mesh");

			//glColor3f(1,1,0);
			//glBegin(GL_TRIANGLES);

			//for(size_t i=0; i<hitTriangleIdx.size(); i++)
			//{
			//	int idx = hitTriangleIdx[i];
			//	glVertex3f( verts[idx+0][0], verts[idx+0][1], verts[idx+0][2] );
			//	glVertex3f(  verts[idx+1][0], verts[idx+1][1], verts[idx+1][2] );
			//	glVertex3f(  verts[idx+2][0], verts[idx+2][1], verts[idx+2][2] );
			//}

			//glEnd();
		}
	}



private:
	CollideableCamera myCamera;
	std::vector< size_t > hitTriangleIdx;
	bool immidiateMode;

	float mouseXSpeed, mouseYSpeed;
	float oldMousx, oldMousy;

	EATLevel level1;

	
};